#include <pin.H>
#include <assert.h>
#include "../HSGlobal.h"
#include "Scheduler.h"
#include "../PowerModels/PowerModel.h"

//TODO: Better interface to checkpointer...
extern void LOAD_EXPLICIT_CHECKPOINT_SCHED( unsigned ckptNo );

const double Scheduler::tickLengthSeconds = 1e-9;
Scheduler::Scheduler( PowerModel *p, Stash *nv ){

  cycleTicks = 0;
  deadTicks = 0;
  currentTick = 0;
  currentCycle = 0;
  FailureBit = false;
  deathID = HSOUTOFBLOCK;
  powerModel = p;
  NV = nv; 
  deadLastTick = false;

}

void Scheduler::dumpStats(FILE *f){

  this->powerModel->dumpStats(f);
  HSLOG(f,"Ticks: %lu\n", this->currentTick);
  HSLOG(f,"Deadticks: %lu\n", this->deadTicks);
  HSLOG(f,"Cycles: %lu\n", this->currentCycle);
  HSLOG(f,"Deaths by try{} block:\n");
  map<unsigned,unsigned long>::iterator i,e;

  for(i = deathsByTryBlock.begin(), 
      e = deathsByTryBlock.end(); i != e; i++){

    if( i->first == HSOUTOFBLOCK ){

      HSLOG(f,"\tOut of Block: %lu\n",i->second);

    }else{

      HSLOG(f,"\tBlock %u: %lu\n",i->first,i->second);

    }

  }

}

//The cost of instructions was based on this document: 
//www.ti.com/sc/docs/products/micro/msp430/userguid/as_5.pdf
//Instructions that hit memory are approximated at 5 cycles
//Other instructions are approximated at 1 cycle.

unsigned long Scheduler::insCost( INT32 insType ){
 
  return 1;

}


unsigned long Scheduler::readCost( ADDRINT inst, ADDRINT opea ){

  //TODO: Implement a cache to determine readCost model?
  return 5;

}

unsigned long Scheduler::writeCost( ADDRINT inst, ADDRINT opea ){

  //TODO: Implement a cache to determine writeCost model?
  return 5;

}
    
void Scheduler::updateTicks( long *insTicks, long fullTickCost, PowerModel::QueryResult *res ){

  this->currentTick++;
  if( res->dead == true ){
    

    if( this->deadLastTick == false){

      this->deadLastTick = true;

      if( this->GET_DEATH_ID() != HSOUTOFBLOCK ){//TODO: Make this a define somewhere

        HSLOG(stderr,"Dying in try block #%lu\n",this->GET_DEATH_ID());

      }else{

        HSLOG(stderr,"Dying out of a try block\n");

      }

      if( this->deathsByTryBlock.find(this->GET_DEATH_ID()) == 
          this->deathsByTryBlock.end() ){

          this->deathsByTryBlock[ this->GET_DEATH_ID() ] = 1;

      }else{

        this->deathsByTryBlock[ this->GET_DEATH_ID() ]++;

      }


    }
    this->deadTicks++;
    *insTicks = fullTickCost;
    cycleTicks = this->ticksPerCycle;

  }else{


    *insTicks = *insTicks - 1;
    if(cycleTicks-- == 0){

      currentCycle++;
      cycleTicks = ticksPerCycle;

    }

    if( this->deadLastTick == true ){

      /*This case means we are revived on this tick*/
      this->deadLastTick = false;

      HSLOG(stderr,"Scheduler revived to current checkpoint\n");
      LOAD_EXPLICIT_CHECKPOINT_SCHED( 0 );

      
      
    }
    this->deadLastTick = false;

  }
 
}

void Scheduler::scheduleInstruction( OperationInfo *oi ){

  long fullTickCost = oi->getLatency();
  long tickCost = fullTickCost;
  this->cycleTicks = ticksPerCycle;
  while( tickCost > 0 ){

    PowerModel::QueryResult *res = 
      powerModel->tick(this->currentTick, oi->getPower());

    this->updateTicks( &tickCost, fullTickCost, res );
 
  }

}

/*scheduleInstruction takes the instruction pointer in
  inst and the cost in cycles to execute that instruction*/
void Scheduler::scheduleInstruction( ADDRINT inst, unsigned long cost ){

  OperationInfo oi(cost * ticksPerCycle, 0);
  scheduleInstruction(&oi);

}

/*Store size bytes of *value's contents into the address Scheduler::NVAddr in NV Mem*/
void Scheduler::STORE_NV(void *value, unsigned long NVAddr, size_t size){ 

  OperationInfo *info = this->NV->store(value, NVAddr, size);
  fprintf(stderr,"welp, that woulda cost %lu time and %f energy\n",info->getLatency(), info->getEnergy());
  scheduleInstruction(info); 

}

/*Read size bytes from Scheduler::NV Mem addr NVAddr and put it in *buf*/
void Scheduler::LOAD_NV(void *buf, unsigned long NVAddr, size_t size){ 

  OperationInfo *info = this->NV->load(buf, NVAddr, size);
  fprintf(stderr,"welp, that woulda cost %lu time and %f energy\n",info->getLatency(), info->getEnergy());
  scheduleInstruction(info); 

}

void Scheduler::GET_RECOVERY_ROUTINE( void *recoveryRtn ){ 

  //sched->GET_RECOVERY_ROUTINE( recoveryRtn );

} 

unsigned long Scheduler::GET_DEATH_ID(){ 

  return this->deathID;

}

void Scheduler::SET_DEATH_ID(unsigned long id){ 

  this->deathID = id;

}

bool Scheduler::GET_FAILURE_BIT(){ 

  return this->FailureBit;

}

void Scheduler::SET_FAILURE_BIT(){ 

  this->FailureBit = true;

} 

void Scheduler::CLEAR_FAILURE_BIT(){ 

  this->FailureBit = false;

} 

/*The functions from here down are implemented in the instrumentation code,
 * when using the Pin-based checkpointing implementation.  That means that
 * checkpointer->STORE_EXPLICIT_CHECKPOINT(...) etc doesn't do anything, but in
 * the instrumentation defined in PinCheckpointer.h/cpp, the code that
 * captures, saves, and restores is listed.  Pin looks for <extern "C"
 * STORE_EXPLICIT_CHECKPOINT>, etc, and adds code before it to do ckpting.*/
void Scheduler::STORE_EXPLICIT_CHECKPOINT( unsigned ckptNo, unsigned long size ){ 

  //fprintf(stderr,"Storing checkpoint %u that is %lu bytes\n",ckptNo, size);
  OperationInfo *info = this->NV->nonResidentStore(size);
  scheduleInstruction(info); 

}

void Scheduler::LOAD_EXPLICIT_CHECKPOINT( unsigned ckptNo, unsigned long size ){ 
  fprintf(stderr,"Loading checkpoint %u that is %lu bytes\n",ckptNo, size);
  //TODO:Add energy and cycle accounting 
  //powerModel->dockForCheckpoint(...)
  OperationInfo *info = this->NV->nonResidentLoad(size);
  scheduleInstruction(info); 

}

void Scheduler::LOAD_CURRENT_CHECKPOINT(){ 
  
  //TODO:Add energy and cycle accounting 

}

void Scheduler::LD_CURR_CP_AFTER_RECOVERY_RTN( void *recoveryRtn ){ 

  //TODO:Add energy and cycle accounting 

}

